﻿/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	public sealed class TopicMetadataXml
	{
		#region Public Properties
		public bool ReadAttributes { get; set; }
		public bool ReadKeywords { get; set; }
		#endregion

		#region Private / Protected
		private readonly string file;
	 	#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TopicMetadataXml" /> class.
		/// </summary>
		public TopicMetadataXml(string file)
		{
			if (string.IsNullOrEmpty(file))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "file");

			this.file = file;
			ReadKeywords = true;
			ReadAttributes = true;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="TopicMetadataXml" /> class.
		/// </summary>
		public TopicMetadataXml(string file, bool readKeywords, bool readAttributes)
		{
			if (string.IsNullOrEmpty(file))
				throw new ArgumentException(Resources.Errors.NullOrEmpty, "file");

			this.file = file;
			ReadKeywords = readKeywords;
			ReadAttributes = readAttributes;
		}
		#endregion

		#region Methods
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unescape")]
		public static string UnescapeKeywordTerm(string term)
		{
			if (term == null)
				return null;
			else
				return term.Replace("%2C", ",").Replace("%3B", ";").Replace("%3A", ":");
		}

		public static string EscapeKeywordTerm(string term)
		{
			if (term == null)
				return null;
			else
				return term.Replace(",", "%2C").Replace(";", "%3B").Replace(":", "%3A");
		}

		[System.Diagnostics.DebuggerHidden]
		public static TopicMetadata Load(string file, string topicId)
		{
			try
			{
				TopicMetadataCollection metadata = new TopicMetadataXml(file).ReadAll();

				if (metadata.Contains(topicId))
					return metadata[topicId];
			}
			catch (System.IO.FileNotFoundException) { }
			catch (System.IO.DirectoryNotFoundException) { }

			return null;
		}

		[System.Diagnostics.DebuggerHidden]
		public static TopicMetadata LoadOrDefault(string file, string topicId)
		{
			try
			{
				TopicMetadataCollection metadata = new TopicMetadataXml(file).ReadAll();

				if (metadata.Contains(topicId))
					return metadata[topicId];
			}
			catch (System.IO.FileNotFoundException) { }
			catch (System.IO.DirectoryNotFoundException) { }

			return new TopicMetadata(topicId);
		}

		[System.Diagnostics.DebuggerHidden]
		public TopicMetadataCollection ReadAll()
		{
			XmlReaderSettings settings = new XmlReaderSettings();
			settings.IgnoreComments = true;
			settings.IgnoreProcessingInstructions = true;
			settings.IgnoreWhitespace = true;

			TopicMetadataCollection metadata = new TopicMetadataCollection();

			using (XmlReader reader = XmlReader.Create(file, settings))
			{
				ReadAll(reader, metadata);
			}

			return metadata;
		}

		private void ReadAll(XmlReader reader, TopicMetadataCollection metadata)
		{
			reader.ReadStartElement("metadata");

			while (reader.IsStartElement("topic"))
			{
				using (XmlReader topicReader = reader.ReadSubtree())
				{
					topicReader.MoveToContent();

					TopicMetadata topic = ReadTopic(topicReader);

					if (topic != null)
						metadata.Add(topic);
				}

				reader.ReadToNextSibling("topic");
			}
		}

		private TopicMetadata ReadTopic(XmlReader reader)
		{
			string topicId = reader.GetAttribute("id");

			if (string.IsNullOrEmpty(topicId))
				return null;

			string title = null, tocTitle = null;
			MetadataCollection attributes = new MetadataCollection();
			KeywordMultiCollection keywords = new KeywordMultiCollection();

			while (reader.Read())
			{
				if (reader.NodeType == XmlNodeType.Element && reader.Depth == 1)
				{
					switch (reader.LocalName)
					{
						case "title":
							title = reader.ReadString();
							break;
						case "tableOfContentsTitle":
							tocTitle = reader.ReadString();
							break;
						case "attribute":
							if (ReadAttributes)
								ReadAttribute(reader, attributes);
							break;
						case "keyword":
							if (ReadKeywords)
								ReadKeyword(reader, keywords);
							break;
					}
				}
			}

			return new TopicMetadata(topicId, title, tocTitle, keywords, attributes);
		}

		private static void ReadAttribute(XmlReader reader, MetadataCollection attributes)
		{
			string name = reader.GetAttribute("name");

			if (!string.IsNullOrEmpty(name))
				attributes.Add(new Metadata(name, reader.ReadString()));
		}

		private static void ReadKeyword(XmlReader reader, KeywordMultiCollection keywords)
		{
			string index = reader.GetAttribute("index");

			if (string.IsNullOrEmpty(index))
				return;

			StringBuilder term = new StringBuilder();
			List<Keyword> nestedKeywords = new List<Keyword>(8);

			using (XmlReader keywordReader = reader.ReadSubtree())
			{
				keywordReader.MoveToContent();

				while (keywordReader.Read())
				{
					if (keywordReader.Depth == 1)
					{
						if (keywordReader.NodeType == XmlNodeType.Text)
						{
							if (term.Length > 0)
								term.Append(" ");

							string value = keywordReader.Value.Trim();

							if (value.Length > 0)
								term.Append(value);
						}
						else if (keywordReader.NodeType == XmlNodeType.Element && keywordReader.LocalName == "keyword")
						{
							string nestedIndex = keywordReader.GetAttribute("index");

							if (string.IsNullOrEmpty(nestedIndex) || string.Equals(nestedIndex, index, StringComparison.OrdinalIgnoreCase))
								nestedKeywords.Add(new Keyword(index, UnescapeKeywordTerm(keywordReader.ReadString())));
						}
					}
				}
			}

			if (term.Length > 0)
				keywords.Add(new KeywordCollection(new Keyword(index, UnescapeKeywordTerm(term.ToString())), nestedKeywords));
		}

		public void WriteCompanionFile(string topicId, string defaultTocTitle, TopicMetadata metadata)
		{
			XmlWriterSettings writerSettings = new XmlWriterSettings();
			writerSettings.Encoding = Encoding.UTF8;
			writerSettings.Indent = true;

			using (XmlWriter writer = XmlWriter.Create(file, writerSettings))
			{
				writer.WriteStartDocument();
				writer.WriteStartElement("metadata");
				writer.WriteAttributeString("fileAssetGuid", topicId);
				writer.WriteAttributeString("assetTypeId", "CompanionFile");

				WriteTopic(writer, metadata, topicId, defaultTocTitle);

				writer.WriteEndElement();
				writer.WriteEndDocument();
			}
		}

		public void WriteAll(TopicMetadataCollection metadata)
		{
			if (metadata == null)
				throw new ArgumentNullException("metadata");

			XmlWriterSettings writerSettings = new XmlWriterSettings();
			writerSettings.Encoding = Encoding.UTF8;
			writerSettings.Indent = true;

			using (XmlWriter writer = XmlWriter.Create(file, writerSettings))
			{
				writer.WriteStartDocument();
				writer.WriteStartElement("metadata");

				foreach (TopicMetadata data in metadata)
				{
					WriteTopic(writer, data, data.TopicId, null);
				}

				writer.WriteEndElement();
				writer.WriteEndDocument();
			}
		}

		public void Write(TopicMetadata metadata)
		{
			if (metadata == null)
				throw new ArgumentNullException("metadata");

			XmlWriterSettings writerSettings = new XmlWriterSettings();
			writerSettings.Encoding = Encoding.UTF8;
			writerSettings.Indent = true;

			using (XmlWriter writer = XmlWriter.Create(file, writerSettings))
			{
				writer.WriteStartDocument();
				writer.WriteStartElement("metadata");

				WriteTopic(writer, metadata, metadata.TopicId, null);

				writer.WriteEndElement();
				writer.WriteEndDocument();
			}
		}

		private static void WriteTopic(XmlWriter writer, TopicMetadata metadata, string topicId, string defaultTocTitle)
		{
			writer.WriteStartElement("topic");
			writer.WriteAttributeString("id", topicId);

			WriteTitles(writer, metadata, defaultTocTitle);

			if (metadata != null)
			{
				WriteAttributes(writer, metadata);
				WriteKeywords(writer, metadata);
			}

			writer.WriteEndElement();
		}

		private static void WriteTitles(XmlWriter writer, TopicMetadata metadata, string defaultTocTitle)
		{
			string title = null, tocTitle = null;

			if (metadata != null)
			{
				tocTitle = metadata.TocTitle;
				title = metadata.Title;
			}

			if (string.IsNullOrEmpty(tocTitle))
				tocTitle = defaultTocTitle;

			if (string.IsNullOrEmpty(title))
				title = tocTitle;

			if (!string.IsNullOrEmpty(title))
			{
				writer.WriteStartElement("title");
				writer.WriteString(title);
				writer.WriteEndElement();
			}

			if (!string.IsNullOrEmpty(tocTitle))
			{
				writer.WriteStartElement("tableOfContentsTitle");
				writer.WriteString(tocTitle);
				writer.WriteEndElement();
			}
		}

		private static void WriteKeywords(XmlWriter writer, TopicMetadata metadata)
		{
			foreach (KeywordCollection keywordGroup in metadata.Keywords)
			{
				Keyword rootKeyword = keywordGroup.Root;

				if (!string.IsNullOrEmpty(rootKeyword.Term))
				{
					writer.WriteStartElement("keyword");
					writer.WriteAttributeString("index", rootKeyword.Index);
					writer.WriteString(EscapeKeywordTerm(rootKeyword.Term));

					foreach (Keyword nestedKeyword in keywordGroup)
					{
						writer.WriteStartElement("keyword");
						writer.WriteAttributeString("index", nestedKeyword.Index);
						writer.WriteString(EscapeKeywordTerm(nestedKeyword.Term));
						writer.WriteEndElement();
					}

					writer.WriteEndElement();
				}
			}
		}

		private static void WriteAttributes(XmlWriter writer, TopicMetadata metadata)
		{
			foreach (Metadata attribute in metadata.Attributes)
			{
				if (attribute != Metadata.Empty)
				{
					writer.WriteStartElement("attribute");
					writer.WriteAttributeString("name", attribute.Name);
					writer.WriteString(attribute.Value);
					writer.WriteEndElement();
				}
			}
		}
		#endregion
	}
}
